/*
 * Copyright (C) 2011, EADS France
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 */
package xowl.gmi.view.diagram;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 *
 * @author Laurent WOUTERS
 */
public class Graph extends Widget {
    private static final Couple minSize = new Couple(100, 100);
    
    public static interface Actions {
        boolean acceptFlavor(java.awt.datatransfer.DataFlavor flavor);
        boolean acceptData(java.awt.datatransfer.DataFlavor flavor, Object data);
        boolean drop(java.awt.datatransfer.DataFlavor flavor, Object data, java.awt.Point location);
    }
    
    private Actions actions;
    private List<GraphNode> childrenNodes;
    private List<GraphConnector> childrenConnectors;
    private Map<GraphNode, Map<GraphNode, List<GraphConnector>>> impactMap;
    private xowl.gmi.view.DropHandler dropHandler;
    private boolean isHovered;
    private boolean accepting;
    
    public xowl.gmi.view.DropHandler onMouseDrop() { return dropHandler; }
    
    public Graph(Actions actions) {
        this.actions = actions;
        this.childrenNodes = new ArrayList<>();
        this.childrenConnectors = new ArrayList<>();
        this.impactMap = new HashMap<>();
        
        this.dropHandler = new xowl.gmi.view.DropHandler() {
            @Override public int getSupportedActions() { return javax.swing.TransferHandler.COPY_OR_MOVE; }
            @Override public boolean acceptFlavor(java.awt.datatransfer.DataFlavor flavor) { return Graph.this.actions.acceptFlavor(flavor); }
            @Override public boolean acceptData(java.awt.datatransfer.DataFlavor flavor, Object data) {
                accepting = Graph.this.actions.acceptData(flavor, data);
                return accepting;
            }
            @Override public boolean drop(java.awt.datatransfer.DataFlavor flavor, Object data, java.awt.Point location) {
                return Graph.this.actions.drop(flavor, data, location);
            }
        };
    }
    
    public void addChild(GraphNode child) {
        childrenNodes.add(child);
        impactMap.put(child, new HashMap<GraphNode, List<GraphConnector>>());
        child.setParentGraph(this);
    }
    
    public void changeLayer(GraphNode child, int layer) {
        childrenNodes.remove(child);
        childrenNodes.add(layer, child);
    }
    
    public void removeChild(GraphNode child) {
        childrenNodes.remove(child);
        impactMap.remove(child);
    }
    
    public void addChild(GraphConnector connector) {
        childrenConnectors.add(connector);
        List<GraphConnector> list = null;
        if (!impactMap.get(connector.getFrom()).containsKey(connector.getTo())) {
            list = new ArrayList<>();
            impactMap.get(connector.getFrom()).put(connector.getTo(), list);
            impactMap.get(connector.getTo()).put(connector.getFrom(), list);
        } else {
            list = impactMap.get(connector.getFrom()).get(connector.getTo());
        }
        list.add(connector);
        connector.resetBounds();
    }
    
    public void removeChild(GraphConnector connector) {
        childrenConnectors.remove(connector);
        impactMap.get(connector.getFrom()).get(connector.getTo()).remove(connector);
    }
    
    public void updateConnectors(GraphNode node) {
        for (GraphNode to : impactMap.get(node).keySet()) {
            List<GraphConnector> connectors = impactMap.get(node).get(to);
            updateConnectors(connectors);
        }
    }
    private void updateConnectors(List<GraphConnector> list) {
        for (GraphConnector connector : list)
            connector.resetBounds();
    }
    
    @Override protected void buildStackChildren(List<Widget> stack, Couple input) {
        for (GraphConnector connector : childrenConnectors)
            if (connector.buildStackAt(stack, input))
                return;
        for (GraphNode gn : childrenNodes)
            if (gn.buildStackAt(stack, input))
                return;
    }
    
    @Override public Couple getMinSize() {
        double maxBottom = 0;
        double maxRight = 0;
        for (Widget child : childrenNodes) {
            Couple dim = child.getMinSize();
            Couple pos = child.getPosition();
            double right = pos.x + dim.x;
            double bottom = pos.y + dim.y;
            if (right > maxRight) maxRight = right;
            if (bottom > maxBottom) maxBottom = bottom;
        }
        return new Couple(Math.max(minSize.x, maxRight), Math.max(minSize.y, maxBottom));
    }
    @Override public Couple getMaxSize() { return MAXSIZE; }
    @Override public Couple layout() {
        dimension = getMinSize();
        for (GraphNode gn : childrenNodes)
            gn.layout();
        for (GraphConnector connector : childrenConnectors)
            connector.layout();
        return dimension;
    }
    @Override public Couple layout(Couple bounds) {
        dimension = getMinSize();
        dimension = new Couple(Math.max(bounds.x, dimension.x), Math.max(bounds.y, dimension.y));
        for (GraphNode gn : childrenNodes)
            gn.layout();
        for (GraphConnector connector : childrenConnectors)
            connector.layout();
        return dimension;
    }
    @Override public void paint(java.awt.Graphics2D g2d, float alpha, DiagramStyle style, Couple offset) {
        if (isHovered && accepting)
            paintBackground(g2d, alpha, style, offset);
        Couple real = position.plus(offset);
        for (int i=childrenNodes.size()-1; i!=-1; i--) {
            GraphNode gn = childrenNodes.get(i);
            gn.paint(g2d, alpha, style, real);
        }
        for (GraphConnector connector : childrenConnectors)
            connector.paint(g2d, alpha, style, real);
    }
    @Override protected void exportSVG(org.w3c.dom.Document doc, org.w3c.dom.Element parent, float alpha, DiagramStyle style, Couple offset) {
        Couple real = position.plus(offset);
        for (int i=childrenNodes.size()-1; i!=-1; i--) {
            GraphNode gn = childrenNodes.get(i);
            gn.exportSVG(doc, parent, alpha * transparency, style, real);
        }
        for (GraphConnector connector : childrenConnectors)
            connector.exportSVG(doc, parent, alpha * transparency, style, real);
    }
    
    @Override public boolean onMouseEnter(UserState state, Couple local) {
        if (state != UserState.Dragging) return false;
        isHovered = true;
        accepting = false;
        return true;
    }
    @Override public boolean onMouseLeave() {
        if (!isHovered) return false; 
        isHovered = false;
        accepting = false;
        return true;
    }
    
    private void paintBackground(java.awt.Graphics2D g2d, float alpha, DiagramStyle style, Couple poff) {
        g2d.setPaint(new java.awt.Color(style.dropColor.getRed(), style.dropColor.getGreen(), style.dropColor.getBlue(), 30));
        g2d.setStroke(new java.awt.BasicStroke(1));
        double bound = dimension.x + dimension.y;
        for (double i=10; i<bound; i+=10) {
            double sx = poff.x + position.x + i;
            double sy = poff.y + position.y;
            double ex = poff.x + position.x;
            double ey = poff.y + position.y + i;
            if (sx > (poff.x + position.x + dimension.x)) {
                double offset = i - dimension.x;
                sx -= offset;
                sy += offset;
            }
            if (ey > (poff.y + position.y + dimension.y)) {
                double offset = i - dimension.y;
                ey -= offset;
                ex += offset;
            }
            g2d.drawLine((int)sx, (int)sy, (int)ex, (int)ey);
        }
    }
}
